home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / python-debian / debian_bundle / deb822.py < prev    next >
Encoding:
Python Source  |  2009-04-15  |  36.4 KB  |  1,054 lines

  1. # vim: fileencoding=utf-8
  2. #
  3. # A python interface for various rfc822-like formatted files used by Debian
  4. # (.changes, .dsc, Packages, Sources, etc)
  5. #
  6. # Copyright (C) 2005-2006  dann frazier <dannf@dannf.org>
  7. # Copyright (C) 2006-2008  John Wright <john@johnwright.org>
  8. # Copyright (C) 2006       Adeodato Sim√≥ <dato@net.com.org.es>
  9. # Copyright (C) 2008       Stefano Zacchiroli <zack@upsilon.cc>
  10. #
  11. # This program is free software; you can redistribute it and/or
  12. # modify it under the terms of the GNU General Public License
  13. # as published by the Free Software Foundation, either version 2
  14. # of the License, or (at your option) any later version.
  15. #
  16. # This program is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19. # GNU General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU General Public License
  22. # along with this program; if not, write to the Free Software
  23. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  24.  
  25.  
  26. try:
  27.     import apt_pkg
  28.     _have_apt_pkg = True
  29. except ImportError:
  30.     _have_apt_pkg = False
  31.  
  32. import new
  33. import re
  34. import string
  35. import sys
  36. import StringIO
  37. import UserDict
  38.  
  39. class OrderedSet(object):
  40.     """A set-like object that preserves order when iterating over it
  41.  
  42.     We use this to keep track of keys in Deb822Dict, because it's much faster
  43.     to look up if a key is in a set than in a list.
  44.     """
  45.  
  46.     def __init__(self, iterable=[]):
  47.         self.__set = set()
  48.         self.__order = []
  49.         for item in iterable:
  50.             self.add(item)
  51.  
  52.     def add(self, item):
  53.         if item not in self:
  54.             # set.add will raise TypeError if something's unhashable, so we
  55.             # don't have to handle that ourselves
  56.             self.__set.add(item)
  57.             self.__order.append(item)
  58.  
  59.     def remove(self, item):
  60.         # set.remove will raise KeyError, so we don't need to handle that
  61.         # ourselves
  62.         self.__set.remove(item)
  63.         self.__order.remove(item)
  64.  
  65.     def __iter__(self):
  66.         # Return an iterator of items in the order they were added
  67.         return iter(self.__order)
  68.  
  69.     def __contains__(self, item):
  70.         # This is what makes OrderedSet faster than using a list to keep track
  71.         # of keys.  Lookup in a set is O(1) instead of O(n) for a list.
  72.         return item in self.__set
  73.  
  74.     ### list-like methods
  75.     append = add
  76.  
  77.     def extend(self, iterable):
  78.         for item in iterable:
  79.             self.add(item)
  80.     ###
  81.  
  82. class Deb822Dict(object, UserDict.DictMixin):
  83.     # Subclassing UserDict.DictMixin because we're overriding so much dict
  84.     # functionality that subclassing dict requires overriding many more than
  85.     # the four methods that DictMixin requires.
  86.     """A dictionary-like object suitable for storing RFC822-like data.
  87.  
  88.     Deb822Dict behaves like a normal dict, except:
  89.         - key lookup is case-insensitive
  90.         - key order is preserved
  91.         - if initialized with a _parsed parameter, it will pull values from
  92.           that dictionary-like object as needed (rather than making a copy).
  93.           The _parsed dict is expected to be able to handle case-insensitive
  94.           keys.
  95.  
  96.     If _parsed is not None, an optional _fields parameter specifies which keys
  97.     in the _parsed dictionary are exposed.
  98.     """
  99.  
  100.     # See the end of the file for the definition of _strI
  101.  
  102.     def __init__(self, _dict=None, _parsed=None, _fields=None):
  103.         self.__dict = {}
  104.         self.__keys = OrderedSet()
  105.         self.__parsed = None
  106.  
  107.         if _dict is not None:
  108.             # _dict may be a dict or a list of two-sized tuples
  109.             if hasattr(_dict, 'items'):
  110.                 items = _dict.items()
  111.             else:
  112.                 items = list(_dict)
  113.  
  114.             try:
  115.                 for k, v in items:
  116.                     self[k] = v
  117.             except ValueError:
  118.                 this = len(self.__keys)
  119.                 len_ = len(items[this])
  120.                 raise ValueError('dictionary update sequence element #%d has '
  121.                     'length %d; 2 is required' % (this, len_))
  122.         
  123.         if _parsed is not None:
  124.             self.__parsed = _parsed
  125.             if _fields is None:
  126.                 self.__keys.extend([ _strI(k) for k in self.__parsed.keys() ])
  127.             else:
  128.                 self.__keys.extend([ _strI(f) for f in _fields if self.__parsed.has_key(f) ])
  129.         
  130.     ### BEGIN DictMixin methods
  131.  
  132.     def __setitem__(self, key, value):
  133.         key = _strI(key)
  134.         self.__keys.add(key)
  135.         self.__dict[key] = value
  136.         
  137.     def __getitem__(self, key):
  138.         key = _strI(key)
  139.         try:
  140.             return self.__dict[key]
  141.         except KeyError:
  142.             if self.__parsed is not None and key in self.__keys:
  143.                 return self.__parsed[key]
  144.             else:
  145.                 raise
  146.  
  147.     def __delitem__(self, key):
  148.         key = _strI(key)
  149.         del self.__dict[key]
  150.         self.__keys.remove(key)
  151.  
  152.     def has_key(self, key):
  153.         key = _strI(key)
  154.         return key in self.__keys
  155.     
  156.     def keys(self):
  157.         return [str(key) for key in self.__keys]
  158.     
  159.     ### END DictMixin methods
  160.  
  161.     def __repr__(self):
  162.         return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
  163.  
  164.     def __eq__(self, other):
  165.         mykeys = self.keys(); mykeys.sort()
  166.         otherkeys = other.keys(); otherkeys.sort()
  167.         if not mykeys == otherkeys:
  168.             return False
  169.  
  170.         for key in mykeys:
  171.             if self[key] != other[key]:
  172.                 return False
  173.  
  174.         # If we got here, everything matched
  175.         return True
  176.  
  177.     def copy(self):
  178.         # Use self.__class__ so this works as expected for subclasses
  179.         copy = self.__class__(self)
  180.         return copy
  181.  
  182.     # TODO implement __str__() and make dump() use that?
  183.  
  184.  
  185. class Deb822(Deb822Dict):
  186.  
  187.     def __init__(self, sequence=None, fields=None, _parsed=None):
  188.         """Create a new Deb822 instance.
  189.  
  190.         :param sequence: a string, or any any object that returns a line of
  191.             input each time, normally a file().  Alternately, sequence can
  192.             be a dict that contains the initial key-value pairs.
  193.  
  194.         :param fields: if given, it is interpreted as a list of fields that
  195.             should be parsed (the rest will be discarded).
  196.  
  197.         :param _parsed: internal parameter.
  198.         """
  199.  
  200.         if hasattr(sequence, 'items'):
  201.             _dict = sequence
  202.             sequence = None
  203.         else:
  204.             _dict = None
  205.         Deb822Dict.__init__(self, _dict=_dict, _parsed=_parsed, _fields=fields)
  206.  
  207.         if sequence is not None:
  208.             try:
  209.                 self._internal_parser(sequence, fields)
  210.             except EOFError:
  211.                 pass
  212.  
  213.         self.gpg_info = None
  214.  
  215.     def iter_paragraphs(cls, sequence, fields=None, use_apt_pkg=True,
  216.                         shared_storage=False):
  217.         """Generator that yields a Deb822 object for each paragraph in sequence.
  218.  
  219.         :param sequence: same as in __init__.
  220.  
  221.         :param fields: likewise.
  222.  
  223.         :param use_apt_pkg: if sequence is a file(), apt_pkg will be used 
  224.             if available to parse the file, since it's much much faster.  Set
  225.             this parameter to False to disable using apt_pkg.
  226.         :param shared_storage: if sequence is a file(), use_apt_pkg is True,
  227.             and shared_storage is True, yielded objects will share storage, so
  228.             they can't be kept across iterations.  (Also, PGP signatures won't
  229.             be stripped.)  By default, this parameter is False, causing a copy
  230.             of the parsed data to be made through each iteration.  Except for
  231.             with raw Deb822 paragraphs (as opposed to _multivalued subclasses),
  232.             the speed gained by setting shared_storage=True is marginal.  This
  233.             parameter has no effect if use_apt_pkg is False or apt_pkg is not
  234.             available.
  235.         """
  236.  
  237.         if _have_apt_pkg and use_apt_pkg and isinstance(sequence, file):
  238.             parser = apt_pkg.ParseTagFile(sequence)
  239.             while parser.Step() == 1:
  240.                 if shared_storage:
  241.                     parsed = parser.Section
  242.                 else:
  243.                     # Since parser.Section doesn't have an items method, we
  244.                     # need to imitate that method here and make a Deb822Dict
  245.                     # from the result in order to preserve order.
  246.                     items = [(key, parser.Section[key])
  247.                              for key in parser.Section.keys()]
  248.                     parsed = Deb822Dict(items)
  249.                 yield cls(fields=fields, _parsed=parsed)
  250.  
  251.         else:
  252.             iterable = iter(sequence)
  253.             x = cls(iterable, fields)
  254.             while len(x) != 0:
  255.                 yield x
  256.                 x = cls(iterable, fields)
  257.  
  258.     iter_paragraphs = classmethod(iter_paragraphs)
  259.  
  260.     ###
  261.  
  262.     def _internal_parser(self, sequence, fields=None):
  263.         single = re.compile("^(?P<key>\S+)\s*:\s*(?P<data>\S.*?)\s*$")
  264.         multi = re.compile("^(?P<key>\S+)\s*:\s*$")
  265.         multidata = re.compile("^\s(?P<data>.+?)\s*$")
  266.  
  267.         wanted_field = lambda f: fields is None or f in fields
  268.  
  269.         if isinstance(sequence, basestring):
  270.             sequence = sequence.splitlines()
  271.  
  272.         curkey = None
  273.         content = ""
  274.         for line in self.gpg_stripped_paragraph(sequence):
  275.             m = single.match(line)
  276.             if m:
  277.                 if curkey:
  278.                     self[curkey] += content
  279.  
  280.                 if not wanted_field(m.group('key')):
  281.                     curkey = None
  282.                     continue
  283.  
  284.                 curkey = m.group('key')
  285.                 self[curkey] = m.group('data')
  286.                 content = ""
  287.                 continue
  288.  
  289.             m = multi.match(line)
  290.             if m:
  291.                 if curkey:
  292.                     self[curkey] += content
  293.  
  294.                 if not wanted_field(m.group('key')):
  295.                     curkey = None
  296.                     continue
  297.  
  298.                 curkey = m.group('key')
  299.                 self[curkey] = ""
  300.                 content = ""
  301.                 continue
  302.  
  303.             m = multidata.match(line)
  304.             if m:
  305.                 content += '\n' + line # XXX not m.group('data')?
  306.                 continue
  307.  
  308.         if curkey:
  309.             self[curkey] += content
  310.  
  311.     def __str__(self):
  312.         return self.dump()
  313.  
  314.     # __repr__ is handled by Deb822Dict
  315.  
  316.     def dump(self, fd=None):
  317.         """Dump the the contents in the original format
  318.  
  319.         If fd is None, return a string.
  320.         """
  321.  
  322.         if fd is None:
  323.             fd = StringIO.StringIO()
  324.             return_string = True
  325.         else:
  326.             return_string = False
  327.         for key, value in self.iteritems():
  328.             if not value or value[0] == '\n':
  329.                 # Avoid trailing whitespace after "Field:" if it's on its own
  330.                 # line or the value is empty
  331.                 # XXX Uh, really print value if value == '\n'?
  332.                 fd.write('%s:%s\n' % (key, value))
  333.             else:
  334.                 fd.write('%s: %s\n' % (key, value))
  335.         if return_string:
  336.             return fd.getvalue()
  337.  
  338.     ###
  339.  
  340.     def isSingleLine(self, s):
  341.         if s.count("\n"):
  342.             return False
  343.         else:
  344.             return True
  345.  
  346.     def isMultiLine(self, s):
  347.         return not self.isSingleLine(s)
  348.  
  349.     def _mergeFields(self, s1, s2):
  350.         if not s2:
  351.             return s1
  352.         if not s1:
  353.             return s2
  354.  
  355.         if self.isSingleLine(s1) and self.isSingleLine(s2):
  356.             ## some fields are delimited by a single space, others
  357.             ## a comma followed by a space.  this heuristic assumes
  358.             ## that there are multiple items in one of the string fields
  359.             ## so that we can pick up on the delimiter being used
  360.             delim = ' '
  361.             if (s1 + s2).count(', '):
  362.                 delim = ', '
  363.  
  364.             L = (s1 + delim + s2).split(delim)
  365.             L.sort()
  366.  
  367.             prev = merged = L[0]
  368.  
  369.             for item in L[1:]:
  370.                 ## skip duplicate entries
  371.                 if item == prev:
  372.                     continue
  373.                 merged = merged + delim + item
  374.                 prev = item
  375.             return merged
  376.  
  377.         if self.isMultiLine(s1) and self.isMultiLine(s2):
  378.             for item in s2.splitlines(True):
  379.                 if item not in s1.splitlines(True):
  380.                     s1 = s1 + "\n" + item
  381.             return s1
  382.  
  383.         raise ValueError
  384.  
  385.     def mergeFields(self, key, d1, d2 = None):
  386.         ## this method can work in two ways - abstract that away
  387.         if d2 == None:
  388.             x1 = self
  389.             x2 = d1
  390.         else:
  391.             x1 = d1
  392.             x2 = d2
  393.  
  394.         ## we only have to do work if both objects contain our key
  395.         ## otherwise, we just take the one that does, or raise an
  396.         ## exception if neither does
  397.         if key in x1 and key in x2:
  398.             merged = self._mergeFields(x1[key], x2[key])
  399.         elif key in x1:
  400.             merged = x1[key]
  401.         elif key in x2:
  402.             merged = x2[key]
  403.         else:
  404.             raise KeyError
  405.  
  406.         ## back to the two different ways - if this method was called
  407.         ## upon an object, update that object in place.
  408.         ## return nothing in this case, to make the author notice a
  409.         ## problem if she assumes the object itself will not be modified
  410.         if d2 == None:
  411.             self[key] = merged
  412.             return None
  413.  
  414.         return merged
  415.     ###
  416.  
  417.     def split_gpg_and_payload(sequence):
  418.         """Return a (gpg_pre, payload, gpg_post) tuple
  419.         
  420.         Each element of the returned tuple is a list of lines (with trailing
  421.         whitespace stripped).
  422.         """
  423.  
  424.         gpg_pre_lines = []
  425.         lines = []
  426.         gpg_post_lines = []
  427.         state = 'SAFE'
  428.         gpgre = re.compile(r'^-----(?P<action>BEGIN|END) PGP (?P<what>[^-]+)-----$')
  429.         blank_line = re.compile('^$')
  430.         first_line = True
  431.  
  432.         for line in sequence:
  433.             line = line.strip('\r\n')
  434.  
  435.             # skip initial blank lines, if any
  436.             if first_line:
  437.                 if blank_line.match(line):
  438.                     continue
  439.                 else:
  440.                     first_line = False
  441.  
  442.             m = gpgre.match(line)
  443.  
  444.             if not m:
  445.                 if state == 'SAFE':
  446.                     if not blank_line.match(line):
  447.                         lines.append(line)
  448.                     else:
  449.                         if not gpg_pre_lines:
  450.                             # There's no gpg signature, so we should stop at
  451.                             # this blank line
  452.                             break
  453.                 elif state == 'SIGNED MESSAGE':
  454.                     if blank_line.match(line):
  455.                         state = 'SAFE'
  456.                     else:
  457.                         gpg_pre_lines.append(line)
  458.                 elif state == 'SIGNATURE':
  459.                     gpg_post_lines.append(line)
  460.             else:
  461.                 if m.group('action') == 'BEGIN':
  462.                     state = m.group('what')
  463.                 elif m.group('action') == 'END':
  464.                     gpg_post_lines.append(line)
  465.                     break
  466.                 if not blank_line.match(line):
  467.                     if not lines:
  468.                         gpg_pre_lines.append(line)
  469.                     else:
  470.                         gpg_post_lines.append(line)
  471.  
  472.         if len(lines):
  473.             return (gpg_pre_lines, lines, gpg_post_lines)
  474.         else:
  475.             raise EOFError('only blank lines found in input')
  476.  
  477.     split_gpg_and_payload = staticmethod(split_gpg_and_payload)
  478.  
  479.     def gpg_stripped_paragraph(cls, sequence):
  480.         return cls.split_gpg_and_payload(sequence)[1]
  481.  
  482.     gpg_stripped_paragraph = classmethod(gpg_stripped_paragraph)
  483.  
  484.     def get_gpg_info(self):
  485.         """Return a GpgInfo object with GPG signature information
  486.  
  487.         This method will raise ValueError if the signature is not available
  488.         (e.g. the original text cannot be found)"""
  489.  
  490.         # raw_text is saved (as a string) only for Changes and Dsc (see
  491.         # _gpg_multivalued.__init__) which is small compared to Packages or
  492.         # Sources which contain no signature
  493.         if not hasattr(self, 'raw_text'):
  494.             raise ValueError, "original text cannot be found"
  495.  
  496.         if self.gpg_info is None:
  497.             self.gpg_info = GpgInfo.from_sequence(self.raw_text)
  498.  
  499.         return self.gpg_info
  500.  
  501. ###
  502.  
  503. # XXX check what happens if input contains more that one signature
  504. class GpgInfo(dict):
  505.     """A wrapper around gnupg parsable output obtained via --status-fd
  506.  
  507.     This class is really a dictionary containing parsed output from gnupg plus
  508.     some methods to make sense of the data.
  509.     Keys are keywords and values are arguments suitably splitted.
  510.     See /usr/share/doc/gnupg/DETAILS.gz"""
  511.  
  512.     # keys with format "key keyid uid"
  513.     uidkeys = ('GOODSIG', 'EXPSIG', 'EXPKEYSIG', 'REVKEYSIG', 'BADSIG')
  514.  
  515.     def valid(self):
  516.         """Is the signature valid?"""
  517.         return self.has_key('GOODSIG') or self.has_key('VALIDSIG')
  518.     
  519. # XXX implement as a property?
  520. # XXX handle utf-8 %-encoding
  521.     def uid(self):
  522.         """Return the primary ID of the signee key, None is not available"""
  523.         pass
  524.  
  525.     @staticmethod
  526.     def from_output(out, err=None):
  527.         """Create a new GpgInfo object from gpg(v) --status-fd output (out) and
  528.         optionally collect stderr as well (err).
  529.         
  530.         Both out and err can be lines in newline-terminated sequence or regular strings."""
  531.  
  532.         n = GpgInfo()
  533.  
  534.         if isinstance(out, basestring):
  535.             out = out.split('\n')
  536.         if isinstance(err, basestring):
  537.             err = err.split('\n')
  538.  
  539.         n.out = out
  540.         n.err = err
  541.         
  542.         header = '[GNUPG:] '
  543.         for l in out:
  544.             if not l.startswith(header):
  545.                 continue
  546.  
  547.             l = l[len(header):]
  548.             l = l.strip('\n')
  549.  
  550.             # str.partition() would be better, 2.5 only though
  551.             s = l.find(' ')
  552.             key = l[:s]
  553.             if key in GpgInfo.uidkeys:
  554.                 # value is "keyid UID", don't split UID
  555.                 value = l[s+1:].split(' ', 1)
  556.             else:
  557.                 value = l[s+1:].split(' ')
  558.  
  559.             n[key] = value
  560.         return n 
  561.  
  562. # XXX how to handle sequences of lines? file() returns \n-terminated
  563.     @staticmethod
  564.     def from_sequence(sequence, keyrings=['/usr/share/keyrings/debian-keyring.gpg'],
  565.             executable=["/usr/bin/gpgv"]):
  566.         """Create a new GpgInfo object from the given sequence.
  567.  
  568.         Sequence is a sequence of lines or a string
  569.         executable is a list of args for subprocess.Popen, the first element being the gpg executable"""
  570.  
  571.         # XXX check for gpg as well and use --verify accordingly?
  572.         args = executable
  573.         #args.extend(["--status-fd", "1", "--no-default-keyring"])
  574.         args.extend(["--status-fd", "1"])
  575.         import os
  576.         [args.extend(["--keyring", k]) for k in keyrings if os.path.isfile(k) and os.access(k, os.R_OK)]
  577.         
  578.         if "--keyring" not in args:
  579.             raise IOError, "cannot access none of given keyrings"
  580.  
  581.         import subprocess
  582.         p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  583.         # XXX what to do with exit code?
  584.  
  585.         if isinstance(sequence, basestring):
  586.             (out, err) = p.communicate(sequence)
  587.         else:
  588.             (out, err) = p.communicate("\n".join(sequence))
  589.  
  590.         return GpgInfo.from_output(out, err)
  591.  
  592.     @staticmethod
  593.     def from_file(target, *args):
  594.         """Create a new GpgInfo object from the given file, calls from_sequence(file(target), *args)"""
  595.         return from_sequence(file(target), *args)
  596.     
  597. ###
  598.  
  599. class PkgRelation(object):
  600.     """Inter-package relationships
  601.  
  602.     Structured representation of the relationships of a package to another,
  603.     i.e. of what can appear in a Deb882 field like Depends, Recommends,
  604.     Suggests, ... (see Debian Policy 7.1).
  605.     """
  606.  
  607.     # XXX *NOT* a real dependency parser, and that is not even a goal here, we
  608.     # just parse as much as we need to split the various parts composing a
  609.     # dependency, checking their correctness wrt policy is out of scope
  610.     __dep_RE = re.compile( \
  611.             r'^\s*(?P<name>[a-zA-Z0-9.+\-]{2,})(\s*\(\s*(?P<relop>[>=<]+)\s*(?P<version>[0-9a-zA-Z:\-+~.]+)\s*\))?(\s*\[(?P<archs>[\s!\w\-]+)\])?\s*$')
  612.     __comma_sep_RE = re.compile(r'\s*,\s*')
  613.     __pipe_sep_RE = re.compile(r'\s*\|\s*')
  614.     __blank_sep_RE = re.compile(r'\s*')
  615.  
  616.     @classmethod
  617.     def parse_relations(cls, raw):
  618.         """Parse a package relationship string (i.e. the value of a field like
  619.         Depends, Recommends, Build-Depends ...)
  620.         """
  621.         def parse_archs(raw):
  622.             # assumption: no space beween '!' and architecture name
  623.             archs = []
  624.             for arch in cls.__blank_sep_RE.split(raw.strip()):
  625.                 if len(arch) and arch[0] == '!':
  626.                     archs.append((False, arch[1:]))
  627.                 else:
  628.                     archs.append((True, arch))
  629.             return archs
  630.  
  631.         def parse_rel(raw):
  632.             match = cls.__dep_RE.match(raw)
  633.             if match:
  634.                 parts = match.groupdict()
  635.                 d = { 'name': parts['name'] }
  636.                 if not (parts['relop'] is None or parts['version'] is None):
  637.                     d['version'] = (parts['relop'], parts['version'])
  638.                 else:
  639.                     d['version'] = None
  640.                 if parts['archs'] is None:
  641.                     d['arch'] = None
  642.                 else:
  643.                     d['arch'] = parse_archs(parts['archs'])
  644.                 return d
  645.             else:
  646.                 print >> sys.stderr, \
  647.                         'deb822.py: WARNING: cannot parse package' \
  648.                         ' relationship "%s", returning it raw' % raw
  649.                 return { 'name': raw, 'version': None, 'arch': None }
  650.  
  651.         tl_deps = cls.__comma_sep_RE.split(raw.strip()) # top-level deps
  652.         cnf = map(cls.__pipe_sep_RE.split, tl_deps)
  653.         return map(lambda or_deps: map(parse_rel, or_deps), cnf)
  654.  
  655.     @staticmethod
  656.     def str(rels):
  657.         """Format to string structured inter-package relationships
  658.         
  659.         Perform the inverse operation of parse_relations, returning a string
  660.         suitable to be written in a package stanza.
  661.         """
  662.         def pp_arch(arch_spec):
  663.             (excl, arch) = arch_spec
  664.             if excl:
  665.                 return arch
  666.             else:
  667.                 return '!' + arch
  668.  
  669.         def pp_atomic_dep(dep):
  670.             s = dep['name']
  671.             if dep.has_key('version') and dep['version'] is not None:
  672.                 s += ' (%s %s)' % dep['version']
  673.             if dep.has_key('arch') and dep['arch'] is not None:
  674.                 s += ' [%s]' % string.join(map(pp_arch, dep['arch']))
  675.             return s
  676.  
  677.         pp_or_dep = lambda deps: string.join(map(pp_atomic_dep, deps), ' | ')
  678.         return string.join(map(pp_or_dep, rels), ', ')
  679.  
  680.  
  681. class _lowercase_dict(dict):
  682.     """Dictionary wrapper which lowercase keys upon lookup."""
  683.  
  684.     def __getitem__(self, key):
  685.         return dict.__getitem__(self, key.lower())
  686.  
  687.  
  688. class _PkgRelationMixin(object):
  689.     """Package relationship mixin
  690.  
  691.     Inheriting from this mixin you can extend a Deb882 object with attributes
  692.     letting you access inter-package relationship in a structured way, rather
  693.     than as strings. For example, while you can usually use pkg['depends'] to
  694.     obtain the Depends string of package pkg, mixing in with this class you
  695.     gain pkg.depends to access Depends as a Pkgrel instance
  696.  
  697.     To use, subclass _PkgRelationMixin from a class with a _relationship_fields
  698.     attribute. It should be a list of field names for which structured access
  699.     is desired; for each of them a method wild be added to the inherited class.
  700.     The method name will be the lowercase version of field name; '-' will be
  701.     mangled as '_'. The method would return relationships in the same format of
  702.     the PkgRelation' relations property.
  703.  
  704.     See Packages and Sources as examples.
  705.     """
  706.  
  707.     def __init__(self, *args, **kwargs):
  708.         self.__relations = _lowercase_dict({})
  709.         self.__parsed_relations = False
  710.         for name in self._relationship_fields:
  711.             # To avoid reimplementing Deb822 key lookup logic we use a really
  712.             # simple dict subclass which just lowercase keys upon lookup. Since
  713.             # dictionary building happens only here, we ensure that all keys
  714.             # are in fact lowercase.
  715.             # With this trick we enable users to use the same key (i.e. field
  716.             # name) of Deb822 objects on the dictionary returned by the
  717.             # relations property.
  718.             keyname = name.lower()
  719.             if self.has_key(name):
  720.                 self.__relations[keyname] = None   # lazy value
  721.                     # all lazy values will be expanded before setting
  722.                     # __parsed_relations to True
  723.             else:
  724.                 self.__relations[keyname] = []
  725.  
  726.     @property
  727.     def relations(self):
  728.         """Return a dictionary of inter-package relationships among the current
  729.         and other packages.
  730.  
  731.         Dictionary keys depend on the package kind. Binary packages have keys
  732.         like 'depends', 'recommends', ... while source packages have keys like
  733.         'build-depends', 'build-depends-indep' and so on. See the Debian policy
  734.         for the comprehensive field list.
  735.  
  736.         Dictionary values are package relationships returned as lists of lists
  737.         of dictionaries (see below for some examples).
  738.  
  739.         The encoding of package relationships is as follows:
  740.         - the top-level lists corresponds to the comma-separated list of
  741.           Deb822, their components form a conjuction, i.e. they have to be
  742.           AND-ed together
  743.         - the inner lists corresponds to the pipe-separated list of Deb822,
  744.           their components form a disjunction, i.e. they have to be OR-ed
  745.           together
  746.         - member of the inner lists are dictionaries with the following keys:
  747.           - name:       package (or virtual package) name
  748.           - version:    A pair <operator, version> if the relationship is
  749.                         versioned, None otherwise. operator is one of "<<",
  750.                         "<=", "=", ">=", ">>"; version is the given version as
  751.                         a string.
  752.           - arch:       A list of pairs <polarity, architecture> if the
  753.                         relationship is architecture specific, None otherwise.
  754.                         Polarity is a boolean (false if the architecture is
  755.                         negated with "!", true otherwise), architecture the
  756.                         Debian archtiecture name as a string.
  757.  
  758.         Examples:
  759.  
  760.           "emacs | emacsen, make, debianutils (>= 1.7)"     becomes
  761.           [ [ {'name': 'emacs'}, {'name': 'emacsen'} ],
  762.             [ {'name': 'make'} ],
  763.             [ {'name': 'debianutils', 'version': ('>=', '1.7')} ] ]
  764.  
  765.           "tcl8.4-dev, procps [!hurd-i386]"                 becomes
  766.           [ [ {'name': 'tcl8.4-dev'} ],
  767.             [ {'name': 'procps', 'arch': (false, 'hurd-i386')} ] ]
  768.         """
  769.         if not self.__parsed_relations:
  770.             lazy_rels = filter(lambda n: self.__relations[n] is None,
  771.                     self.__relations.keys())
  772.             for n in lazy_rels:
  773.                 self.__relations[n] = PkgRelation.parse_relations(self[n])
  774.             self.__parsed_relations = True
  775.         return self.__relations
  776.  
  777. class _multivalued(Deb822):
  778.     """A class with (R/W) support for multivalued fields.
  779.  
  780.     To use, create a subclass with a _multivalued_fields attribute.  It should
  781.     be a dictionary with *lower-case* keys, with lists of human-readable
  782.     identifiers of the fields as the values.  Please see Dsc, Changes, and
  783.     PdiffIndex as examples.
  784.     """
  785.  
  786.     def __init__(self, *args, **kwargs):
  787.         Deb822.__init__(self, *args, **kwargs)
  788.  
  789.         for field, fields in self._multivalued_fields.items():
  790.             try:
  791.                 contents = self[field]
  792.             except KeyError:
  793.                 continue
  794.  
  795.             if self.isMultiLine(contents):
  796.                 self[field] = []
  797.                 updater_method = self[field].append
  798.             else:
  799.                 self[field] = Deb822Dict()
  800.                 updater_method = self[field].update
  801.  
  802.             for line in filter(None, contents.splitlines()):
  803.                 updater_method(Deb822Dict(zip(fields, line.split())))
  804.  
  805.     def dump(self, fd=None):
  806.         """Dump the contents in the original format
  807.  
  808.         If fd is None, return a string.
  809.         """
  810.         
  811.         if fd is None:
  812.             fd = StringIO.StringIO()
  813.             return_string = True
  814.         else:
  815.             return_string = False
  816.         for key in self.keys():
  817.             keyl = key.lower()
  818.             if keyl not in self._multivalued_fields:
  819.                 value = self[key]
  820.                 if not value or value[0] == '\n':
  821.                     # XXX Uh, really print value if value == '\n'?
  822.                     fd.write('%s:%s\n' % (key, value))
  823.                 else:
  824.                     fd.write('%s: %s\n' % (key, value))
  825.             else:
  826.                 fd.write(key + ":")
  827.                 if hasattr(self[key], 'keys'): # single-line
  828.                     array = [ self[key] ]
  829.                 else: # multi-line
  830.                     fd.write("\n")
  831.                     array = self[key]
  832.  
  833.                 order = self._multivalued_fields[keyl]
  834.                 try:
  835.                     field_lengths = self._fixed_field_lengths
  836.                 except AttributeError:
  837.                     field_lengths = {}
  838.                 for item in array:
  839.                     for x in order:
  840.                         raw_value = str(item[x])
  841.                         try:
  842.                             length = field_lengths[keyl][x]
  843.                         except KeyError:
  844.                             value = raw_value
  845.                         else:
  846.                             value = (length - len(raw_value)) * " " + raw_value
  847.                         fd.write(" %s" % value)
  848.                     fd.write("\n")
  849.         if return_string:
  850.             return fd.getvalue()
  851.  
  852.  
  853. ###
  854.  
  855.  
  856. class _gpg_multivalued(_multivalued):
  857.     """A _multivalued class that can support gpg signed objects
  858.  
  859.     This class's feature is that it stores the raw text before parsing so that
  860.     gpg can verify the signature.  Use it just like you would use the
  861.     _multivalued class.
  862.  
  863.     This class only stores raw text if it is given a raw string, or if it
  864.     detects a gpg signature when given a file or sequence of lines (see
  865.     Deb822.split_gpg_and_payload for details).
  866.     """
  867.  
  868.     def __init__(self, *args, **kwargs):
  869.         try:
  870.             sequence = args[0]
  871.         except IndexError:
  872.             sequence = kwargs.get("sequence", None)
  873.  
  874.         if sequence is not None:
  875.             if isinstance(sequence, basestring):
  876.                 self.raw_text = sequence
  877.             elif hasattr(sequence, "items"):
  878.                 # sequence is actually a dict(-like) object, so we don't have
  879.                 # the raw text.
  880.                 pass
  881.             else:
  882.                 try:
  883.                     gpg_pre_lines, lines, gpg_post_lines = \
  884.                             self.split_gpg_and_payload(sequence)
  885.                 except EOFError:
  886.                     # Empty input
  887.                     gpg_pre_lines = lines = gpg_post_lines = []
  888.                 if gpg_pre_lines and gpg_post_lines:
  889.                     raw_text = StringIO.StringIO()
  890.                     raw_text.write("\n".join(gpg_pre_lines))
  891.                     raw_text.write("\n\n")
  892.                     raw_text.write("\n".join(lines))
  893.                     raw_text.write("\n\n")
  894.                     raw_text.write("\n".join(gpg_post_lines))
  895.                     self.raw_text = raw_text.getvalue()
  896.                 try:
  897.                     args = list(args)
  898.                     args[0] = lines
  899.                 except IndexError:
  900.                     kwargs["sequence"] = lines
  901.  
  902.         _multivalued.__init__(self, *args, **kwargs)
  903.  
  904.  
  905. class Dsc(_gpg_multivalued):
  906.     _multivalued_fields = {
  907.         "files": [ "md5sum", "size", "name" ],
  908.         "checksums-sha1": ["sha1", "size", "name"],
  909.         "checksums-sha256": ["sha256", "size", "name"],
  910.     }
  911.  
  912.  
  913. class Changes(_gpg_multivalued):
  914.     _multivalued_fields = {
  915.         "files": [ "md5sum", "size", "section", "priority", "name" ],
  916.         "checksums-sha1": ["sha1", "size", "name"],
  917.         "checksums-sha256": ["sha256", "size", "name"],
  918.     }
  919.  
  920.     def get_pool_path(self):
  921.         """Return the path in the pool where the files would be installed"""
  922.     
  923.         # This is based on the section listed for the first file.  While
  924.         # it is possible, I think, for a package to provide files in multiple
  925.         # sections, I haven't seen it in practice.  In any case, this should
  926.         # probably detect such a situation and complain, or return a list...
  927.         
  928.         s = self['files'][0]['section']
  929.  
  930.         try:
  931.             section, subsection = s.split('/')
  932.         except ValueError:
  933.             # main is implicit
  934.             section = 'main'
  935.  
  936.         if self['source'].startswith('lib'):
  937.             subdir = self['source'][:4]
  938.         else:
  939.             subdir = self['source'][0]
  940.  
  941.         return 'pool/%s/%s/%s' % (section, subdir, self['source'])
  942.  
  943.  
  944. class PdiffIndex(_multivalued):
  945.     _multivalued_fields = {
  946.         "sha1-current": [ "SHA1", "size" ],
  947.         "sha1-history": [ "SHA1", "size", "date" ],
  948.         "sha1-patches": [ "SHA1", "size", "date" ],
  949.     }
  950.  
  951.     @property
  952.     def _fixed_field_lengths(self):
  953.         fixed_field_lengths = {}
  954.         for key in self._multivalued_fields:
  955.             if hasattr(self[key], 'keys'):
  956.                 # Not multi-line -- don't need to compute the field length for
  957.                 # this one
  958.                 continue
  959.             length = self._get_size_field_length(key)
  960.             fixed_field_lengths[key] = {"size": length}
  961.         return fixed_field_lengths
  962.  
  963.     def _get_size_field_length(self, key):
  964.         lengths = [len(str(item['size'])) for item in self[key]]
  965.         return max(lengths)
  966.  
  967.  
  968. class Release(_multivalued):
  969.     """Represents a Release file
  970.  
  971.     Set the size_field_behavior attribute to "dak" to make the size field
  972.     length only as long as the longest actual value.  The default,
  973.     "apt-ftparchive" makes the field 16 characters long regardless.
  974.     """
  975.     # FIXME: Add support for detecting the behavior of the input, if
  976.     # constructed from actual 822 text.
  977.  
  978.     _multivalued_fields = {
  979.         "md5sum": [ "md5sum", "size", "name" ],
  980.         "sha1": [ "sha1", "size", "name" ],
  981.         "sha256": [ "sha256", "size", "name" ],
  982.     }
  983.  
  984.     __size_field_behavior = "apt-ftparchive"
  985.     def set_size_field_behavior(self, value):
  986.         if value not in ["apt-ftparchive", "dak"]:
  987.             raise ValueError("size_field_behavior must be either "
  988.                              "'apt-ftparchive' or 'dak'")
  989.         else:
  990.             self.__size_field_behavior = value
  991.     size_field_behavior = property(lambda self: self.__size_field_behavior,
  992.                                    set_size_field_behavior)
  993.  
  994.     @property
  995.     def _fixed_field_lengths(self):
  996.         fixed_field_lengths = {}
  997.         for key in self._multivalued_fields:
  998.             length = self._get_size_field_length(key)
  999.             fixed_field_lengths[key] = {"size": length}
  1000.         return fixed_field_lengths
  1001.  
  1002.     def _get_size_field_length(self, key):
  1003.         if self.size_field_behavior == "apt-ftparchive":
  1004.             return 16
  1005.         elif self.size_field_behavior == "dak":
  1006.             lengths = [len(str(item['size'])) for item in self[key]]
  1007.             return max(lengths)
  1008.  
  1009.  
  1010. class Sources(Dsc, _PkgRelationMixin):
  1011.     """Represent an APT source package list"""
  1012.  
  1013.     _relationship_fields = [ 'build-depends', 'build-depends-indep',
  1014.             'build-conflicts', 'build-conflicts-indep' ]
  1015.  
  1016.     def __init__(self, *args, **kwargs):
  1017.         Dsc.__init__(self, *args, **kwargs)
  1018.         _PkgRelationMixin.__init__(self, *args, **kwargs)
  1019.  
  1020.  
  1021. class Packages(Deb822, _PkgRelationMixin):
  1022.     """Represent an APT binary package list"""
  1023.  
  1024.     _relationship_fields = [ 'depends', 'pre-depends', 'recommends',
  1025.             'suggests', 'breaks', 'conflicts', 'provides', 'replaces',
  1026.             'enhances' ]
  1027.  
  1028.     def __init__(self, *args, **kwargs):
  1029.         Deb822.__init__(self, *args, **kwargs)
  1030.         _PkgRelationMixin.__init__(self, *args, **kwargs)
  1031.  
  1032. ###
  1033.  
  1034. class _CaseInsensitiveString(str):
  1035.     """Case insensitive string.
  1036.     """
  1037.  
  1038.     def __new__(cls, str_):
  1039.         s = str.__new__(cls, str_)
  1040.         s.str_lower = str_.lower()
  1041.         s.str_lower_hash = hash(s.str_lower)
  1042.         return s
  1043.  
  1044.     def __hash__(self):
  1045.         return self.str_lower_hash
  1046.  
  1047.     def __eq__(self, other):
  1048.         return self.str_lower == other.lower()
  1049.  
  1050.     def lower(self):
  1051.         return self.str_lower
  1052.  
  1053. _strI = _CaseInsensitiveString
  1054.